/*
>> NeighbourhoodToPixelOp.s
>>
>> Neighbourhood operation skeleton, allowing buffer and scalar inputs, and
>> buffer outputs. Adapted from NeighbourhoodToPixelOp.py and PixelToPixelOp.s
>>
>> Author: Wouter Caarls
>> Date:   06-04-2005
*/

#include "ilpstrategies.h"
#include "kernelsplit.h"

stratego code flattenarrays(code param, code type, int offset, code body)
{
  strategies
    main = ?(param, type, offset, body); !(body, (param, type, offset));
      try(env-alltd(FlattenArrays)); bottomup(try(apply-conc))

  rules
    FlattenArrays:
      (ArrayIndex(ArrayIndex(p, i1), i2), (p, t, end)) ->
      c:exp |[ ~exp:p [ ( - ~exp:i1 ) + ~exp:end ] [ _ii + ~exp:i2 ] ]|
      where (<id>(t) => IntConst("0") + <id>(t) => IntConst("1"))

    FlattenArrays:
      (p, (p, t, _)) -> p
      where (not(<id>(t) => IntConst("0") + <id>(t) => IntConst("1")))

    apply-conc:
      Conc(list1, list2) -> <conc>(list1, list2)
}


void RecursiveNeighbourhoodToPixelOp(argcode_t *argcode, int arguments, code body)
{
int _ii, _jj;
int preallocate, postallocate;
int maxdiscard = 0, maxstate = 0;
promote int _fi = -1, _stride;
promote extents_t _extents[2];
code splitbody[];

/* Initialize extents */
for (_jj=0; _jj < 2; _jj++)
{
  _extents[_jj].start = 100;
  _extents[_jj].end = -100;
  _extents[_jj].size = -100;
}

for (_ii=0; _ii < arguments; _ii++)
{
  if (argcode[_ii].argtype == 0 || argcode[_ii].argtype == ARG_STREAM_OUT)
  {
    /* Adjust extents */
    for (_jj=0; _jj < 2; _jj++)
    {
      _extents[_jj].start = MIN(_extents[_jj].start, argcode[_ii].extents[_jj].start);
      _extents[_jj].end = MAX(_extents[_jj].end, argcode[_ii].extents[_jj].end);
      _extents[_jj].size = MAX(_extents[_jj].size, argcode[_ii].extents[_jj].size);
    }

    if (argcode[_ii].argtype == ARG_STREAM_IN)
    {
      /* Adjust first input */
      if (_fi == -1)
        _fi = _ii;
      if (argcode[_ii].extents[0].size > argcode[_fi].extents[0].size)
        _fi = _ii;
    }
  }
}

/* Find state variables for discard/delay, etc. */
preallocate = MAX(-_extents[0].start, 0);
postallocate = MAX(_extents[0].end, 0);
for (_ii=0; _ii < arguments; _ii++)
{
  argcode[_ii].state.delay = 0;

  if (argcode[_ii].argtype == 0)
  {
    argcode[_ii].extents[0].discard = preallocate + argcode[_ii].extents[0].start;
    argcode[_ii].extents[0].delay = postallocate - argcode[_ii].extents[0].end;
    argcode[_ii].state.buffer = argcode[_ii].extents[0].size + argcode[_ii].extents[0].delay;
    argcode[_ii].state.state = argcode[_ii].state.buffer - 1;

    maxdiscard = MAX(argcode[_ii].extents[0].discard, maxdiscard);

    argcode[_ii].tokens.initial = argcode[_ii].extents[0].discard + argcode[_ii].state.buffer - 1;
    argcode[_ii].tokens.final = 0;
  }
  else
  {
    if (argcode[_ii].argtype == ARG_STREAM_OUT)
      argcode[_ii].state.buffer = argcode[_ii].extents[0].size;
    else
      argcode[_ii].state.buffer = 1;

    argcode[_ii].state.state = 1;
    argcode[_ii].extents[0].discard = argcode[_ii].extents[0].delay = 0;

    argcode[_ii].tokens.initial = preallocate - argcode[_ii].state.buffer + 1;
    argcode[_ii].tokens.final = postallocate + argcode[_ii].state.buffer - 1;
  }

  maxstate = MAX(argcode[_ii].state.buffer, maxstate);
}

declarearguments(argcode, arguments);
for (_ii=0; _ii < arguments; _ii++)
{

  body = flattenarrays(argcode[_ii].id, argcode[_ii].argtype,
                       argcode[_ii].extents[0].end+argcode[_ii].extents[0].delay, body);
}

splitbody = @kernelsplit(body);

/* Get stream strides */
/* NOTE: ASSUMES ALL STRIDES ARE EQUAL */
_stride = BufferGetStride(_fi);

prologue {
  int _ii, _jj;
  int all = MAX(maxdiscard+maxstate-1, preallocate);

  $@splitbody[0];

  for (_jj=0; _jj < all; _jj++)
  {
    for (_ii=0; _ii < arguments; _ii++)
    {
      if (argcode[_ii].argtype == 0)
      {
        if (argcode[_ii].extents[0].discard > _jj)
        {
          /* Discard */
          BufferPeek(_ii, (void**)&(@argcode[_ii].id)[0], _stride*sizeof(argcode[_ii].datatype), 1);
          BufferReleasePeeked(_ii, _stride*sizeof(argcode[_ii].datatype));
        }
        else if (argcode[_ii].extents[0].discard + argcode[_ii].state.buffer - 1 > _jj)
        {
          /* Runin */
          BufferPeek(_ii,
                     (void**)&(@argcode[_ii].id)[(argcode[_ii].state.buffer-1) -
                                           (_jj-argcode[_ii].extents[0].discard)],
                     _stride*sizeof(argcode[_ii].datatype), 1);
        }
      }
      else if (argcode[_ii].argtype == 1)
      {
        /* Preallocate */
        if (preallocate > _jj)
        {
          BufferAllocate(_ii, (void**)&(@argcode[_ii].id)[MAX(preallocate-_jj, argcode[_ii].state.buffer-1)], _stride*sizeof(argcode[_ii].datatype),
                         1);
          memset((@argcode[_ii].id)[MAX(preallocate-_jj, argcode[_ii].state.buffer-1)], 0, _stride*sizeof(argcode[_ii].datatype));

          if (preallocate - argcode[_ii].state.buffer + 1 > _jj)
            BufferReleaseAllocated(_ii, _stride*sizeof(argcode[_ii].datatype));
        }
      }
    }
  }
};

repeat {
  int _ii;

  for (_ii=0; _ii < arguments; _ii++)
    if (argcode[_ii].argtype == 1)
    {
      memset(&(@argcode[_ii].id)[0][0], 0, -_extents[1].start*sizeof(argcode[_ii].datatype));
      memset(&(@argcode[_ii].id)[0][lbl_hpixels-_extents[1].end], 0, _extents[1].end*sizeof(argcode[_ii].datatype));
    }

  /* Run kernel */
#pragma TCS_unroll=16
  for (_ii=-_extents[1].start; _ii < lbl_hpixels-_extents[1].end; _ii++)
    $@splitbody[1];

  circlepointers(argcode, arguments);
};

epilogue {
  int _ii, _jj;
  int all = MAX(maxstate-1, postallocate);

  for (_jj=0; _jj < all; _jj++)
  {
    for (_ii=0; _ii < arguments; _ii++)
    {
      if (argcode[_ii].argtype == 0)
      {
        if (argcode[_ii].state.buffer-1 > _jj)
        {
          /* Runout */
          BufferReleasePeeked(_ii, _stride*sizeof(argcode[_ii].datatype));
        }
      }
      else if (argcode[_ii].argtype == 1)
      {
	if (argcode[_ii].state.buffer-1 > _jj)
	{
	  /* Runout */
	  BufferReleaseAllocated(_ii, _stride*sizeof(argcode[_ii].datatype));
	}

        if (postallocate > _jj)
        {
          /* Postallocate */
          BufferAllocate(_ii, (void**)&(@argcode[_ii].id)[0], _stride*sizeof(argcode[_ii].datatype),
                         1);
          memset((@argcode[_ii].id)[0], 0, _stride*sizeof(argcode[_ii].datatype));
          BufferReleaseAllocated(_ii, _stride*sizeof(argcode[_ii].datatype));
        }
      }
    }
  }

  finalizearguments(argcode, arguments);
  @$@splitbody[2];
};
}
